!! Copyright (C) 2009,2010,2011,2012  Marco Restelli
!!
!! This file is part of:
!!   LDGH -- Local Hybridizable Discontinuous Galerkin toolkit
!!
!! LDGH is free software: you can redistribute it and/or modify it
!! under the terms of the GNU General Public License as published by
!! the Free Software Foundation, either version 3 of the License, or
!! (at your option) any later version.
!!
!! LDGH is distributed in the hope that it will be useful, but WITHOUT
!! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
!! or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
!! License for more details.
!!
!! You should have received a copy of the GNU General Public License
!! along with LDGH. If not, see <http://www.gnu.org/licenses/>.
!!
!! author: Marco Restelli                   <marco.restelli@gmail.com>


!>\brief
!! This module provides a unified interface to the specific test case
!! modules.
!!
!! \n
!!
!! The specific test case modules, such as \c mod_bubble_test, should
!! never be accessed directly, because this would result in very
!! cumbersome code. A much cleaner solution is using this module as
!! unique interface to all the test case modules, so that when a new
!! test case is added it suffices to change this module.
!! 
!! More precisely, to add a new test case:
!! <ol>
!!
!!  <li> write a new module \c mod_testname whose public interface is
!!   given by:
!!   <ul>
!!    <li>\c mod_testname_constructor:  constructor
!!    <li>\c mod_testname_destructor:   destructor
!!    <li>\c test_name:     name of the test case (\c character)
!!    <li>\c test_description: short description (\c character)
!!    <li>\c coeff_diff:    diffusion coefficient
!!    <li>\c coeff_adv:     advection coefficient
!!    <li>\c coeff_xiadv:   potential of the advective field (only
!!     necessary if computing the exponential fitting reconstruction)
!!    <li>\c coeff_re:      reaction coefficient
!!    <li>\c coeff_f:       right hand side
!!    <li>\c coeff_dir:     Dirichlet datum (only used if there are
!!     Dirichlet bcs)
!!    <li>\c coeff_neu:     Neumann datum (only used if there are
!!     Neumann/Robin bcs)
!!    <li>\c coeff_rob:     Robin coefficient (only used if there are
!!     Neumann/Robin bcs)
!!    <li>\c coeff_alpha:   Fracture transmission coefficient (only
!!     used when solving problems with fractures)
!!   </ul>
!!   The following functions are optional and are used only if the
!!   errors are computed
!!   <ul>
!!    <li>\c coeff_lam:     exact solution, primal variable
!!    <li>\c coeff_q:       exact solution, total flux (dual variable)
!!    <li>\c coeff_divq:    exact solution, divergence of the total flux
!!   </ul>
!!   For time dependent problems, the initial state is specified by
!!   <ul>
!!    <li>\c coeff_init:    initial state for time dependent problems
!!     (primal variable)
!!   </ul>
!! 
!!  <li> use the new module in the present one;
!!
!!  <li> include the proper reference in the \c casename case blocks
!!
!!  <li> add the new module into the Makefile:
!!   <ul>
!!    <li> add the new module in the variable \c OBJ_TEST
!!    <li> add a target for the new module.
!!   </ul>
!!
!! </ol>
!<----------------------------------------------------------------------
module mod_testcases

!-----------------------------------------------------------------------

 use mod_messages, only: &
   mod_messages_initialized, &
   error,   &
   warning, &
   info

 use mod_kinds, only: &
   mod_kinds_initialized, &
   wp

 use mod_bubble_test, only: &
   mod_bubble_test_constructor, &
   mod_bubble_test_destructor,  &
   bubble_test_name  => test_name, &
   bubble_test_description => test_description,&
   bubble_coeff_diff => coeff_diff,&
   bubble_coeff_adv  => coeff_adv, &
   bubble_coeff_xiadv=> coeff_xiadv,&
   bubble_coeff_re   => coeff_re,  &
   bubble_coeff_f    => coeff_f,   &
   bubble_coeff_dir  => coeff_dir, &
   bubble_coeff_neu  => coeff_neu, &
   bubble_coeff_rob  => coeff_rob, &
   bubble_coeff_lam  => coeff_lam, &
   bubble_coeff_q    => coeff_q,   &
   bubble_coeff_divq => coeff_divq

 use mod_smithhutton_test, only: &
   mod_smithhutton_test_constructor, &
   mod_smithhutton_test_destructor,  &
   smithhutton_test_name  => test_name, &
   smithhutton_test_description => test_description,&
   smithhutton_coeff_diff => coeff_diff,&
   smithhutton_coeff_adv  => coeff_adv, &
   smithhutton_coeff_re   => coeff_re,  &
   smithhutton_coeff_f    => coeff_f,   &
   smithhutton_coeff_dir  => coeff_dir, &
   smithhutton_coeff_neu  => coeff_neu, &
   smithhutton_coeff_rob  => coeff_rob, &
   smithhutton_coeff_init => coeff_init

 use mod_zunino2009_test, only: &
   mod_zunino2009_test_constructor, &
   mod_zunino2009_test_destructor,  &
   zunino2009_test_name  => test_name, &
   zunino2009_test_description => test_description, &
   zunino2009_coeff_diff => coeff_diff,&
   zunino2009_coeff_adv  => coeff_adv, &
   zunino2009_coeff_re   => coeff_re,  &
   zunino2009_coeff_f    => coeff_f,   &
   zunino2009_coeff_dir  => coeff_dir, &
   zunino2009_coeff_neu  => coeff_neu, &
   zunino2009_coeff_rob  => coeff_rob

 use mod_sbrot_test, only: &
   mod_sbrot_test_constructor, &
   mod_sbrot_test_destructor,  &
   sbrot_test_name  => test_name, &
   sbrot_test_description => test_description,&
   sbrot_coeff_diff => coeff_diff,&
   sbrot_coeff_adv  => coeff_adv, &
   sbrot_coeff_re   => coeff_re,  &
   sbrot_coeff_f    => coeff_f,   &
   sbrot_coeff_dir  => coeff_dir, &
   sbrot_coeff_neu  => coeff_neu, &
   sbrot_coeff_rob  => coeff_rob, &
   sbrot_coeff_init => coeff_init

 use mod_darcy_test, only: &
   mod_darcy_test_constructor, &
   mod_darcy_test_destructor,  &
   darcy_test_name  => test_name, &
   darcy_test_description => test_description,&
   darcy_coeff_diff => coeff_diff,&
   darcy_coeff_adv  => coeff_adv, &
   darcy_coeff_re   => coeff_re,  &
   darcy_coeff_f    => coeff_f,   &
   darcy_coeff_dir  => coeff_dir, &
   darcy_coeff_neu  => coeff_neu, &
   darcy_coeff_rob  => coeff_rob

 use mod_fracture_test, only: &
   mod_fracture_test_constructor, &
   mod_fracture_test_destructor,  &
   fracture_test_name  => test_name, &
   fracture_test_description => test_description,&
   fracture_coeff_diff => coeff_diff,&
   fracture_coeff_adv  => coeff_adv, &
   fracture_coeff_re   => coeff_re,  &
   fracture_coeff_f    => coeff_f,   &
   fracture_coeff_dir  => coeff_dir, &
   fracture_coeff_neu  => coeff_neu, &
   fracture_coeff_rob  => coeff_rob, &
   fracture_coeff_alpha=> coeff_alpha

!-----------------------------------------------------------------------
 
 implicit none

!-----------------------------------------------------------------------

! Module interface

 public :: &
   mod_testcases_constructor, &
   mod_testcases_destructor,  &
   mod_testcases_initialized, &
   test_name,   & ! name of the test case
   test_description,& ! short description of the test case
   i_coeff_diff,& ! interface coeff_diff
   coeff_diff,  & ! diffusion coefficient
   i_coeff_adv, & ! interface coeff_adv
   coeff_adv,   & ! advection coefficient
   i_coeff_xiadv,&! interface of coeff_xiadv
   coeff_xiadv, & ! potential of the advection coefficient (if any)
   coeff_re,    & ! reaction coefficient
   coeff_f,     & ! rhs
   coeff_dir,   & ! Dirichlet datum
   coeff_neu,   & ! Neumann datum
   coeff_rob,   & ! Robin coefficient
   coeff_alpha, & ! fracture permeability
   i_coeff_lam, & ! interface coeff_lam
   coeff_lam,   & ! lambda analytic solution
   i_coeff_q,   & ! interface coeff_q
   coeff_q,     & ! total flux q
   i_coeff_divq,& ! interface coeff_divq
   coeff_divq,  & ! divergence of q
   coeff_init     ! initial state for time dependent problems

 private

!-----------------------------------------------------------------------

 ! Module types and parameters
 abstract interface
  pure function i_coeff_diff(x) result(mu)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: mu(size(x,1),size(x,1),size(x,2))
  end function i_coeff_diff
 end interface
 abstract interface
  pure function i_coeff_adv(x) result(a)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: a(size(x,1),size(x,2))
  end function i_coeff_adv
 end interface
 abstract interface
  pure function i_coeff_xiadv(x) result(xi)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: xi(size(x,2))
  end function i_coeff_xiadv
 end interface
 abstract interface
  pure function i_coeff_re(x) result(sigma)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: sigma(size(x,2))
  end function i_coeff_re
 end interface
 abstract interface
  pure function i_coeff_f(x) result(f)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: f(size(x,2))
  end function i_coeff_f
 end interface
 abstract interface
  pure function i_coeff_dir(x,breg) result(d)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   integer, intent(in) :: breg
   real(wp) :: d(size(x,2))
  end function i_coeff_dir
 end interface
 abstract interface
  pure function i_coeff_neu(x,breg) result(h)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   integer, intent(in) :: breg
   real(wp) :: h(size(x,2))
  end function i_coeff_neu
 end interface
 abstract interface
  pure function i_coeff_rob(x,breg) result(g)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   integer, intent(in) :: breg
   real(wp) :: g(size(x,2))
  end function i_coeff_rob
 end interface
 abstract interface
  pure function i_coeff_alpha(x) result(alpha)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: alpha(size(x,2))
  end function i_coeff_alpha
 end interface
 abstract interface
  pure function i_coeff_lam(x) result(lam)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: lam(size(x,2))
  end function i_coeff_lam
 end interface
 abstract interface
  pure function i_coeff_q(x) result(q)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: q(size(x,1),size(x,2))
  end function i_coeff_q
 end interface
 abstract interface
  pure function i_coeff_divq(x) result(dq)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: dq(size(x,2))
  end function i_coeff_divq
 end interface
 abstract interface
  pure function i_coeff_init(x) result(u)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: u(size(x,2))
  end function i_coeff_init
 end interface

 ! Module variables
 ! public members
 character(len=10000), protected ::  &
   test_name
 character(len=10000), allocatable, protected :: &
   test_description(:)
 procedure(i_coeff_diff ), pointer :: coeff_diff  => null()
 procedure(i_coeff_adv  ), pointer :: coeff_adv   => null()
 procedure(i_coeff_xiadv), pointer :: coeff_xiadv => null()
 procedure(i_coeff_re   ), pointer :: coeff_re    => null()
 procedure(i_coeff_f    ), pointer :: coeff_f     => null()
 procedure(i_coeff_dir  ), pointer :: coeff_dir   => null()
 procedure(i_coeff_neu  ), pointer :: coeff_neu   => null()
 procedure(i_coeff_rob  ), pointer :: coeff_rob   => null()
 procedure(i_coeff_alpha), pointer :: coeff_alpha => null()
 procedure(i_coeff_lam  ), pointer :: coeff_lam   => null()
 procedure(i_coeff_q    ), pointer :: coeff_q     => null()
 procedure(i_coeff_divq ), pointer :: coeff_divq  => null()
 procedure(i_coeff_init ), pointer :: coeff_init  => null()
 ! compiler bug: both ifort and gfortran refuse the following code
 !protected :: coeff_diff, coeff_adv, coeff_xiadv, coeff_re, coeff_f, &
 !  coeff_dir, coeff_neu, coeff_rob, coeff_lam, coeff_q, coeff_divq,  &
 !  coeff_init

 logical, protected ::               &
   mod_testcases_initialized = .false.
 ! private members
 character(len=*), parameter :: &
   this_mod_name = 'mod_testcases'

!-----------------------------------------------------------------------

contains

!-----------------------------------------------------------------------

 subroutine mod_testcases_constructor(testname,d)
  character(len=*), intent(in) :: testname
  integer, intent(in) :: d

  integer :: i
  character(len=10000) :: message
  character(len=*), parameter :: &
    this_sub_name = 'constructor'

   !Consistency checks ---------------------------
   if( (mod_messages_initialized.eqv..false.) .or. &
       (mod_kinds_initialized.eqv..false.) ) then
     call error(this_sub_name,this_mod_name, &
                'Not all the required modules are initialized.')
   endif
   if(mod_testcases_initialized.eqv..true.) then
     call warning(this_sub_name,this_mod_name, &
                  'Module is already initialized.')
   endif
   !----------------------------------------------
   
   casename: select case(testname)

    case(bubble_test_name)
     call mod_bubble_test_constructor(d)
     test_name = bubble_test_name
     allocate(test_description(size(bubble_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(bubble_test_description(i))
     enddo
     coeff_diff  => bubble_coeff_diff
     coeff_adv   => bubble_coeff_adv
     coeff_xiadv => bubble_coeff_xiadv
     coeff_re    => bubble_coeff_re
     coeff_f     => bubble_coeff_f
     coeff_dir   => bubble_coeff_dir
     coeff_neu   => bubble_coeff_neu
     coeff_rob   => bubble_coeff_rob
     coeff_lam   => bubble_coeff_lam
     coeff_q     => bubble_coeff_q
     coeff_divq  => bubble_coeff_divq

    case(smithhutton_test_name)
     call mod_smithhutton_test_constructor()
     test_name = smithhutton_test_name
     allocate(test_description(size(smithhutton_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(smithhutton_test_description(i))
     enddo
     coeff_diff  => smithhutton_coeff_diff
     coeff_adv   => smithhutton_coeff_adv
     coeff_re    => smithhutton_coeff_re
     coeff_f     => smithhutton_coeff_f
     coeff_dir   => smithhutton_coeff_dir
     coeff_neu   => smithhutton_coeff_neu
     coeff_rob   => smithhutton_coeff_rob
     coeff_init  => smithhutton_coeff_init

    case(zunino2009_test_name)
     call mod_zunino2009_test_constructor()
     test_name = zunino2009_test_name
     allocate(test_description(size(zunino2009_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(zunino2009_test_description(i))
     enddo
     coeff_diff  => zunino2009_coeff_diff
     coeff_adv   => zunino2009_coeff_adv
     coeff_re    => zunino2009_coeff_re
     coeff_f     => zunino2009_coeff_f
     coeff_dir   => zunino2009_coeff_dir
     coeff_neu   => zunino2009_coeff_neu
     coeff_rob   => zunino2009_coeff_rob

    case(sbrot_test_name)
     call mod_sbrot_test_constructor()
     test_name = sbrot_test_name
     allocate(test_description(size(sbrot_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(sbrot_test_description(i))
     enddo
     coeff_diff  => sbrot_coeff_diff
     coeff_adv   => sbrot_coeff_adv
     coeff_re    => sbrot_coeff_re
     coeff_f     => sbrot_coeff_f
     coeff_dir   => sbrot_coeff_dir
     coeff_neu   => sbrot_coeff_neu
     coeff_rob   => sbrot_coeff_rob
     coeff_init  => sbrot_coeff_init

    case(darcy_test_name)
     call mod_darcy_test_constructor(d)
     test_name = darcy_test_name
     allocate(test_description(size(darcy_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(darcy_test_description(i))
     enddo
     coeff_diff  => darcy_coeff_diff
     coeff_adv   => darcy_coeff_adv
     coeff_re    => darcy_coeff_re
     coeff_f     => darcy_coeff_f
     coeff_dir   => darcy_coeff_dir
     coeff_neu   => darcy_coeff_neu
     coeff_rob   => darcy_coeff_rob

    case(fracture_test_name)
     call mod_fracture_test_constructor(d)
     test_name = fracture_test_name
     allocate(test_description(size(fracture_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(fracture_test_description(i))
     enddo
     coeff_diff  => fracture_coeff_diff
     coeff_adv   => fracture_coeff_adv
     coeff_re    => fracture_coeff_re
     coeff_f     => fracture_coeff_f
     coeff_dir   => fracture_coeff_dir
     coeff_neu   => fracture_coeff_neu
     coeff_rob   => fracture_coeff_rob
     coeff_alpha => fracture_coeff_alpha

    case default
     write(message,'(a,a,a)') 'Unknown test case "',trim(testname),'"'
     call error(this_sub_name,this_mod_name,message)
   end select casename

   write(message,'(a,a,a)') 'Test case: "',trim(test_name),'"'
   call info(this_sub_name,this_mod_name,message)

   mod_testcases_initialized = .true.
 end subroutine mod_testcases_constructor

!-----------------------------------------------------------------------
 
 subroutine mod_testcases_destructor()
  character(len=*), parameter :: &
    this_sub_name = 'destructor'
   
   !Consistency checks ---------------------------
   if(mod_testcases_initialized.eqv..false.) then
     call error(this_sub_name,this_mod_name, &
                'This module is not initialized.')
   endif
   !----------------------------------------------

   casename: select case(trim(test_name))
    case(bubble_test_name)
     call mod_bubble_test_destructor()
    case(smithhutton_test_name)
     call mod_smithhutton_test_destructor()
    case(zunino2009_test_name)
     call mod_zunino2009_test_destructor()
    case(sbrot_test_name)
     call mod_sbrot_test_destructor()
    case(darcy_test_name)
     call mod_darcy_test_destructor()
    case(fracture_test_name)
     call mod_fracture_test_destructor()
   end select casename

   ! nullify the procedure pointers
   nullify( coeff_diff,coeff_adv,coeff_xiadv,coeff_re,coeff_f,   &
     coeff_dir,coeff_neu,coeff_rob,coeff_lam,coeff_q,coeff_divq, &
     coeff_init )

   deallocate(test_description)

   mod_testcases_initialized = .false.
 end subroutine mod_testcases_destructor

!-----------------------------------------------------------------------
 
end module mod_testcases

